This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / routes / [handle] / +layout.ts
3.4 kB 94 lines
1import { error, redirect } from "@sveltejs/kit"; 2import { createBobbinClient } from "$lib/api/client"; 3import type { MiniDoc } from "$lib/api/identity"; 4import { getProfile, type ProfileRecord } from "$lib/api/records"; 5import { 6 enrich, 7 countOf, 8 viewerUriOf, 9 TYPE_COUNT, 10 TYPE_VIEWER, 11 type LinkDescriptor, 12 type LinkSource 13} from "$lib/api/enrich"; 14import { toHttpError, httpStatusFor } from "$lib/api/load"; 15import { ClientResponseError } from "$lib/api/client"; 16import { rkeyFromUri } from "$lib/api/uri"; 17import type { ProfileCounts } from "$lib/components/profile/types"; 18import type { LayoutLoad } from "./$types"; 19 20const REPOS: LinkSource = "sh.tangled.repo:subject"; 21const STRINGS: LinkSource = "sh.tangled.string:subject"; 22const STARS: LinkSource = "sh.tangled.feed.star:.repo"; 23const FOLLOWERS: LinkSource = "sh.tangled.graph.follow:subject"; 24const FOLLOWING: LinkSource = "sh.tangled.graph.follow:.repo"; 25const VOUCHES: LinkSource = "sh.tangled.graph.vouch:subject"; 26 27const COUNT_DESCRIPTORS: LinkDescriptor[] = [ 28 REPOS, 29 STRINGS, 30 STARS, 31 FOLLOWERS, 32 FOLLOWING, 33 VOUCHES 34].map((source) => ({ source, type: TYPE_COUNT })); 35 36export const load: LayoutLoad = async (event) => { 37 const parent = await event.parent(); 38 const identifier = decodeURIComponent(event.params.handle); 39 40 // rejects bare words so unrelated paths 404 instead of resolving as actors 41 if (!identifier.startsWith("did:") && !identifier.includes(".")) { 42 error(404, "Not found"); 43 } 44 45 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); 46 const viewerDid = parent.auth?.did; 47 const resolved = await enrich<MiniDoc>(ctx, { 48 xrpc: "com.bad-example.identity.resolveMiniDoc", 49 params: { identifier }, 50 enrich: [ 51 ...COUNT_DESCRIPTORS.map((descriptor) => ({ ...descriptor, targets: ["did"] })), 52 ...(viewerDid ? [{ source: FOLLOWERS, type: TYPE_VIEWER, targets: ["did"] }] : []) 53 ], 54 ...(viewerDid ? { viewer: viewerDid } : {}) 55 }).catch((cause) => toHttpError(cause, "Could not resolve user")); 56 const doc = resolved.output; 57 58 // redirects dids and stale handles to the canonical handle url 59 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null; 60 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) { 61 redirect(307, `/${canonical}${event.url.search}`); 62 } 63 64 const did = doc.did; 65 66 const profile: ProfileRecord | null = await getProfile(ctx, did) 67 .then((view) => view.value) 68 .catch((cause) => { 69 if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null; 70 return toHttpError(cause, "Could not load profile"); 71 }); 72 73 const counts: ProfileCounts = { 74 repos: countOf(resolved.data, did, REPOS), 75 strings: countOf(resolved.data, did, STRINGS), 76 stars: countOf(resolved.data, did, STARS), 77 followers: countOf(resolved.data, did, FOLLOWERS), 78 following: countOf(resolved.data, did, FOLLOWING), 79 vouches: countOf(resolved.data, did, VOUCHES) 80 }; 81 const viewerFollowUri = 82 viewerDid && viewerDid !== did ? viewerUriOf(resolved.data, did, FOLLOWERS) : null; 83 const viewerFollowRkey = viewerFollowUri ? rkeyFromUri(viewerFollowUri) : viewerFollowUri; 84 85 const notJoined = !profile && Object.values(counts).every((n) => n === 0); 86 87 return { 88 identity: { did, handle: doc.handle }, 89 profile, 90 counts, 91 viewerFollowRkey, 92 notJoined 93 }; 94};